可视化全链路日志追踪
总第523篇
2022年 第040篇
可观测性作为系统高可用的重要保障,已经成为系统建设中不可或缺的一环。然而随着业务逻辑的日益复杂,传统的ELK方案在日志搜集、筛选和分析等方面愈加耗时耗力,而分布式会话跟踪方案虽然基于追踪能力完善了日志的串联,但更聚焦于调用链路,也难以直接应用于高效的业务追踪。
本文介绍了可视化全链路日志追踪的新方案,它以业务链路为载体,通过有效组织业务每次执行的日志,实现了执行现场的可视化还原,支持问题的高效定位。1. 背景
1.1 业务系统日益复杂
1.2 业务追踪面临挑战
2. 可视化全链路日志追踪
2.1 设计思路
2.2 通用方案
3. 大众点评内容平台实践
3.1 业务特点与挑战
3.2 实践与成果
4. 总结与展望
1. 背景
1.1 业务系统日益复杂
1.2 业务追踪面临挑战
1.2.1 传统的ELK方案
日志搜集繁琐:虽然ES提供了日志检索的能力,但是日志数据往往是缺乏结构性的文本段,很难快速完整地搜集到全部相关的日志。
日志筛选困难:不同业务场景、业务逻辑之间存在重叠,重叠逻辑打印的业务日志可能相互干扰,难以从中筛选出正确的关联日志。
日志分析耗时:搜集到的日志只是一条条离散的数据,只能阅读代码,再结合逻辑,由人工对日志进行串联分析,尽可能地还原出现场。
1.2.2 分布式会话跟踪方案
1.2.3 总结
2. 可视化全链路日志追踪
2.1 设计思路
逻辑节点:业务系统的众多逻辑可以按照业务功能进行拆分,形成一个个相互独立的业务逻辑单元,即逻辑节点,可以是本地方法(如下图5的“判断逻辑”节点)也可以是RPC等远程调用方法(如下图5的“逻辑A”节点)。
逻辑链路:业务系统对外支撑着众多的业务场景,每个业务场景对应一个完整的业务流程,可以抽象为由逻辑节点组合而成的逻辑链路,如下图5中的逻辑链路就准确完整地描述了“审核业务场景”。
通过在执行线程和网络通信中持续地透传参数,实现在业务逻辑执行的同时,不中断地传递链路和节点的标识,实现离散日志的染色。
基于标识,染色的离散日志会被动态串联至正在执行的节点,逐渐汇聚出完整的逻辑链路,最终实现业务执行现场的高效组织和可视化展示。
2.2 通用方案
2.2.1 链路定义
{
"nodeName": "A",
"nodeType": "rpc"
},
{
"nodeName": "Fork",
"nodeType": "fork",
"forkNodes": [
[
{
"nodeName": "B",
"nodeType": "rpc"
}
],
[
{
"nodeName": "C",
"nodeType": "local"
}
]
]
},
{
"nodeName": "Join",
"nodeType": "join",
"joinOnList": [
"B",
"C"
]
},
{
"nodeName": "D",
"nodeType": "decision",
"decisionCases": {
"true": [
{
"nodeName": "E",
"nodeType": "rpc"
}
]
},
"defaultCase": [
{
"nodeName": "F",
"nodeType": "rpc"
}
]
}
]
2.2.2 链路染色
链路唯一标识 = 业务标识 + 场景标识 + 执行标识 (三个标识共同决定“某个业务场景下的某次执行”) 业务标识:赋予链路业务含义,例如“用户id”、“活动id”等等。
场景标识:赋予链路场景含义,例如当前场景是“逻辑链路1”。
执行标识:赋予链路执行含义,例如只涉及单次调用时,可以直接选择“traceId”;涉及多次调用时则,根据业务逻辑选取多次调用相同的“公共id”。
节点唯一标识 = 链路唯一标识 + 节点名称 (两个标识共同决定“某个业务场景下的某次执行中的某个逻辑节点”) 节点名称:DSL中预设的节点唯一名称,如“A”。
当“A”节点触发执行,则开始在后续链路和节点中传递串联标识,随着业务流程的执行,逐步完成整个链路的染色。
当标识传递至“E”节点时,则表示“D”条件分支的判断结果是“true”,同时动态地将“E”节点串联至已执行的链路中。
2.2.3 链路上报
2.2.4 链路存储
链路日志:链路单次执行中,从开始节点和结束节点的日志中提取的链路基本信息,包含链路类型、链路元信息、链路开始/结束时间等。
节点日志:链路单次执行中,已执行节点的基本信息,包含节点名称、节点状态、节点开始/结束时间等。
业务日志:链路单次执行中,已执行节点中的业务日志信息,包含日志级别、日志时间、日志数据等。
3. 大众点评内容平台实践
3.1 业务特点与挑战
内容的生产方:希望生产的内容能在更多的渠道分发,收获更多的流量,被消费者所喜爱。
内容的治理方:希望作为“防火墙”过滤出合法合规的内容,同时整合机器和人工能力,丰富内容属性。
内容的消费方:希望获得满足其个性化需求的内容,能够吸引其种草,或辅助其做出消费决策。
统一接入:统一内容数据模型,对接不同的内容生产方,将异构的内容转化为内容平台通用的数据模型。
统一处理:统一处理能力建设,积累并完善通用的机器处理和人工运营能力,保证内容合法合规,属性丰富。
统一输出:统一输出门槛建设,对接不同的内容消费方,为下游提供规范且满足其个性化需求的内容数据。
业务场景多:业务流程涉及多个不同的业务场景,且逻辑各异,例如实时接入、人工运营、分发重算等图中列出的部分场景。
逻辑节点多:业务场景涉及众多的逻辑节点,且不同内容类型节点各异,例如同样是实时接入场景,笔记内容和直播内容在执行的逻辑节点上存在较大差异。
触发执行多:业务场景会被多次触发执行,且由于来源不同,逻辑也会存在差异,例如笔记内容被作者编辑、被系统审核等等后,都会触发实时接入场景的重新执行。
3.2 实践与成果
3.2.1 实践
模仿slf4j-api:工具包的实现在slf4j框架之上,并模仿slf4j-api对外提供相同的API,因此使用方无学习成本。 屏蔽内部细节,内部封装一系列的链路日志上报逻辑,屏蔽染色等细节,降低使用方的开发成本。 上报判断: 判断链路标识:无标识时,进行兜底的日志上报,防止日志丢失。
判断上报方式:有标识时,支持日志和RPC中转两种上报方式。
日志组装:实现参数占位、异常堆栈输出等功能,并将相关数据组装为Trace对象,便于进行统一的收集和处理。 异常上报:通过ErrorAPI主动上报异常,兼容原日志上报中ErrorAppender。 日志上报:适配Log4j2日志框架实现最终的日志上报。
LOGGER.error("update struct failed, param:{}", GsonUtils.toJson(structRequest), e);
// 替换后:全链路日志上报
TraceLogger.error("update struct failed, param:{}", GsonUtils.toJson(structRequest), e);
public Response realTimeInputLink(long contentId) {
// 链路开始:传递串联标识(业务标识 + 场景标识 + 执行标识)
TraceUtils.passLinkMark("contentId_type_uuid");
// ...
// 本地调用(API上报节点日志)
TraceUtils.reportNode("contentStore", contentId, StatusEnums.RUNNING)
contentStore(contentId);
TraceUtils.reportNode("contentStore", structResp, StatusEnums.COMPLETED)
// ...
// 远程调用
Response processResp = picProcess(contentId);
// ...
}
// AOP上报节点日志
@TraceNode(nodeName="picProcess")
public Response picProcess(long contentId) {
// 图片处理业务逻辑
// 业务日志数据上报
TraceLogger.warn("picProcess failed, contentId:{}", contentId);
}
3.2.2 成果
接入成本低:DSL配置配合简单的日志上报改造,即可快速接入。
追踪范围广:任意一条内容的所有逻辑链路,均可被追踪。
使用效率高:管理后台支持链路和日志的可视化查询展示,简单快捷。
4. 总结与展望
5. 参考文献
[1] Metrics, tracing, and logging [2] ELK Stack: Elasticsearch, Logstash, Kibana | Elastic [3] Dapper, a Large-Scale Distributed Systems Tracing Infrastructure [4] OpenZipkin · A distributed tracing system [5] 分布式会话跟踪系统架构设计与实践 [6] 凤凰架构-可观测性 [7] 万字破解云原生可观测性
6. 作者及团队简介
阅读更多